﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net;
using System.Net.Sockets;
using System.Threading;
using FreeRecorderExplorer.Properties;
using System.Diagnostics;
using System.IO;
using System.Windows;
using System.ComponentModel;

namespace FreeRecorderExplorer
{
    public class Server : INotifyPropertyChanged
    {                    
        TcpListener listener;
        TcpClient client;
        static AutoResetEvent are = new AutoResetEvent(false);

        private readonly string DEVICE = "DEVICE:";
        private readonly string FILENAME = "FILENAME:";
        private readonly string SIZE = "SIZE:";


        const int BUFFER_SIZE = 8192;

        private readonly string MSG_SYN = "FREE_RECORDER SYN";
        private readonly string MSG_LST = "FREE_RECORDER LST";
        private readonly string MSG_REC = "FREE_RECORDER REC";
        private readonly string MSG_REC_NOTFOUND = "FREE_RECORDER REC_NOTFOUND";

        bool isDeviceConnected;

        public bool IsDeviceConnected
        {
          get { return isDeviceConnected; }
          private set
          { 
              isDeviceConnected = value;
              NotifyPropertyChanged("IsDeviceConnected");
          }
        }

        string connectedDeviceName;

        public string ConnectedDeviceName
        {
            get { return connectedDeviceName; }
            private set
            {
                connectedDeviceName = value;
                NotifyPropertyChanged("ConnectedDeviceName");
            }
        }

        IPEndPoint local_endpoint;
        public IPEndPoint LocalEndpoint
        {
            get
            {
                return local_endpoint;
            }
            private set
            {
                local_endpoint = value;
                NotifyPropertyChanged("LocalEndpoint");
            }
        }

        private string filenameReq;

        public event EventHandler<RecordingListReceivedEventArgs> RecordingListReceived;
        public event EventHandler DownloadStarted;
        public event EventHandler DownloadEnded;
        public event EventHandler<DownloadingProgressChangedArgs> DownloadingProgressChanged;
        public event EventHandler ErrorDisconnected;


        public Server()
        {
            IsDeviceConnected = false;
        }

        public void Start()
        {
            //var addrs = Dns.GetHostAddresses(Dns.GetHostName());
            if (listener == null)
            {
                if (Settings.Default.IPAddress == "")
                    listener = new TcpListener(IPAddress.Any, Settings.Default.Port);
                else
                    listener = new TcpListener(IPAddress.Parse(Settings.Default.IPAddress), Settings.Default.Port);
            }
                
            listener.Start();
            LocalEndpoint = (IPEndPoint)listener.LocalEndpoint;

            while (true)
            {
                if (client != null) client.Close();

                Debug.Write("Waiting for a connection... ");

                client = listener.AcceptTcpClient();
                Debug.WriteLine("Connected");

                NetworkStream ns = client.GetStream();
                StreamReader reader = new StreamReader(ns);

                try
                {
                    string msg = reader.ReadLine();

                    if (msg == MSG_SYN)
                    {
                        string device = reader.ReadLine();
                        Debug.WriteLine(device);
                        if (device.Substring(0, DEVICE.Length) != DEVICE)
                            throw new ServerException("Unknown message");
                        IsDeviceConnected = true;
                        ConnectedDeviceName = device.Substring(DEVICE.Length);

                        string sizestr = reader.ReadLine();

                        if (sizestr.Substring(0, SIZE.Length) != SIZE)
                            throw new ServerException("Invalid response from mobile phone");

                        int reclistSize = int.Parse(sizestr.Substring(SIZE.Length));

                        if (reclistSize <= 0)
                            throw new ServerException("Invalid response from mobile phone");
                        
                        readRecordings(ns, reclistSize);

                        while (true)
                        {
                            Debug.WriteLine("waiting for download request...");
                            are.WaitOne(-1);
                            if (!IsDeviceConnected) break;
                            Debug.WriteLine("download request has been set");
                            sendRecordingRequest(ns);
                            recvFile(ns); 
                        }                        
                       
                    }
                }
                catch (Exception e)
                {
                    displayMsg(e.Message);
                    IsDeviceConnected = false;
                    ConnectedDeviceName = null;
                    OnErrorDisconnected(new EventArgs());
                }

            }

        }

        private void sendRecordingRequest(NetworkStream ns)
        {
            string msg = MSG_REC + "\r\n" + FILENAME + filenameReq + "\r\n";
            var data = Encoding.UTF8.GetBytes(msg);
            ns.Write(data, 0, data.Length);
        }


        public void End()
        {
            if (listener != null)
            {
                listener.Stop();
            }
            if (client != null)
            {
                client.Close();
            }
            IsDeviceConnected = false;
            ConnectedDeviceName = null;
            LocalEndpoint = null;
        }

        private void readRecordings(NetworkStream stream, int reclistSize)
        {
                          
            var b = new byte[client.ReceiveBufferSize];
            int size;
            MemoryStream ms = new MemoryStream();
            int recvSize = 0;
            do
            {
                size = stream.Read(b, 0, b.Length);
                ms.Write(b, 0, size);
                recvSize += size;
                Debug.WriteLine("{0} byte received, overall: {1}", size, recvSize);
            } while (recvSize < reclistSize && size > 0);

            //do
            //{
            //    var socket = client.Client;
            //    SocketError sockerr;
            //    size = socket.Receive(b, 0, b.Length, SocketFlags.None, out sockerr);
            //    if (sockerr != SocketError.Success)
            //        throw new ServerException("Socket Error: " + sockerr.ToString());

            //    ms.Write(b, 0, size);
            //    recvSize += size;
            //    Debug.WriteLine("{0} byte received, overall: {1}", size, recvSize);
            //} while (recvSize < reclistSize && size > 0);

            //var socket = client.Client;
            //SocketError sockerr;
            //bool timeout = false;
            //AsyncCallback callback = delegate(IAsyncResult a)
            //{
            //    if (timeout) return;
            //    else timeout = true;
            //    are.Set();                        
            //};
            //do
            //{
            //    timeout = false;
            //    var res = socket.BeginReceive(b, 0, b.Length, SocketFlags.None, out sockerr, callback, null);
            //    are.WaitOne(5000);
            //    if (!timeout)
            //    {
            //        timeout = true;
            //        throw new ServerException("Receiving data failed.");
            //    }
            //    size = socket.EndReceive(res, out sockerr);

            //    if (sockerr != SocketError.Success)
            //        throw new ServerException("Socket Error: " + sockerr.ToString());

            //    ms.Write(b, 0, size);
            //    recvSize += size;
            //    Debug.WriteLine("{0} byte received, overall: {1}", size, recvSize);
            //} while (recvSize < reclistSize && size > 0);

            if (recvSize != reclistSize)
            {
                throw new ServerException("Recording list has not been downloaded correctly");
            }

            ms.Position = 0;

            RecordsCollection recs;
            try
            {
                recs = RecordsCollection.Deserialize(ms);
            }
            catch (Exception)
            {
                var tmp = File.Create("tmp");
                var buf = ms.ToArray();
                tmp.Write(buf, 0, buf.Length);
                tmp.Seek(0, SeekOrigin.Begin);
                recs = RecordsCollection.Deserialize(tmp);
            }

            OnRecordingListReceived(new RecordingListReceivedEventArgs(){ Recordings = recs});           
        }

        public void DownloadRecording(string filename)
        {
            filenameReq = filename;
            are.Set();
            Debug.WriteLine("download clicked");
        }

        private void recvFile(NetworkStream ns)
        {
            StreamReader reader = new StreamReader(ns);

            string msg = reader.ReadLine();
            if (msg != MSG_REC && msg != MSG_REC_NOTFOUND)
                throw new ServerException("Invalid response from mobile phone");

            if (msg == MSG_REC_NOTFOUND)
            {
                string sizestr = reader.ReadLine();

                if (sizestr.Substring(0, SIZE.Length) != SIZE)
                    throw new ServerException("Invalid response from mobile phone");

                int reclistSize = int.Parse(sizestr.Substring(SIZE.Length));

                if (reclistSize <= 0)
                    throw new ServerException("Invalid response from mobile phone");
                readRecordings(ns, reclistSize);
                displayMsg("File has been deleted. New recording list received.");
                return;
            }
            string filename = reader.ReadLine();
            Debug.WriteLine(filename);
            
            if (filename.Substring(0, FILENAME.Length) != FILENAME)
                throw new ServerException("Invalid response from mobile phone");
            
            filename = filename.Substring(FILENAME.Length);            
            if (filename != filenameReq)
                throw new ServerException("Invalid response from mobile phone");

             string size = reader.ReadLine();
            
            if (size.Substring(0, SIZE.Length) != SIZE)
                throw new ServerException("Invalid response from mobile phone");
            long fileSize = long.Parse(size.Substring(SIZE.Length));
            
            if (fileSize <= 0)
                throw new ServerException("Invalid response from mobile phone");

            string path;
            path = (Settings.Default.DownloadPath != string.Empty ? 
                Settings.Default.DownloadPath : Directory.GetCurrentDirectory());

            string allpath = Path.Combine(path, filename);
            using (FileStream fs = new FileStream(allpath, FileMode.OpenOrCreate, FileAccess.Write))
            {
                OnDownloadStarted(new EventArgs());
                
                double percent = fileSize / 100.0;
                int to_percent = 0;
                int percents = 0;
                
                var b = new byte[client.ReceiveBufferSize];
                int s;
                int recvSize = 0;
                ns.ReadTimeout = 5000;
                do 
                {
                    s = ns.Read(b, 0, b.Length);

                    to_percent += s;
                    if (to_percent >= percent)
                    {
                        percents += (int)Math.Round(to_percent / percent);
                        OnDownloadingProgressChanged(new DownloadingProgressChangedArgs() { Percentage =  percents });
                        to_percent = 0;
                    }
                    
                    recvSize += s;
                    Debug.WriteLine("{0} byte received, overall: {1}", s, recvSize);
                    fs.Write(b, 0, s);        
                } while (recvSize < fileSize && s > 0);
                ns.ReadTimeout = Timeout.Infinite;

                OnDownloadEnded(new EventArgs());
                if ((int)fs.Length != fileSize)
                {
                    fs.Close();
                    File.Delete(allpath);
                    throw new ServerException("Received file has not been downloaded correctly, so it has been deleted");
                }
            };
        }

        protected void OnRecordingListReceived(RecordingListReceivedEventArgs args)
        {
            if (RecordingListReceived != null)
            {
                Application.Current.Dispatcher.BeginInvoke(new Action(() =>
                {
                    RecordingListReceived(this, args);
                }));
            }
        }

        protected void OnDownloadStarted(EventArgs args)
        {
            if (DownloadStarted != null)
            {
                Application.Current.Dispatcher.BeginInvoke(new Action(() =>
                {
                    DownloadStarted(this, args);
                }));
            }
                
        }

        protected void OnDownloadEnded(EventArgs args)
        {
            if (DownloadEnded != null)
            {
                Application.Current.Dispatcher.BeginInvoke(new Action(() =>
                {
                    DownloadEnded(this, args);
                }));
            }
        }

        protected void OnDownloadingProgressChanged(DownloadingProgressChangedArgs args)
        {
            if (DownloadingProgressChanged != null)
            {
                Application.Current.Dispatcher.BeginInvoke(new Action(() =>
                {
                    DownloadingProgressChanged(this, args);
                }));
            }
        }

        protected void OnErrorDisconnected(EventArgs args)
        {
            if (ErrorDisconnected != null)
            {
                Application.Current.Dispatcher.BeginInvoke(new Action(() =>
                {
                    ErrorDisconnected(this, args);
                }));
            }
        }

        private void displayMsg(string msg)
        {
            App.Current.Dispatcher.BeginInvoke(new Action(() =>
            {
                MessageBox.Show(msg);
            }));                   
        }

        public event PropertyChangedEventHandler PropertyChanged;

        protected void NotifyPropertyChanged(string msg)
        {
            if (PropertyChanged != null)
            {
                App.Current.Dispatcher.BeginInvoke(new Action(() =>
                {
                    PropertyChanged(this, new PropertyChangedEventArgs(msg));
                }));                   
            }
        }
    }
    
    public class ServerException : SystemException
    {
        public ServerException() { }
        public ServerException(string msg) : base(msg) { }
    }

    public class RecordingListReceivedEventArgs : EventArgs
    {
        public RecordsCollection Recordings {get; set;}
    }

    public class DownloadingProgressChangedArgs : EventArgs
    {
        public int Percentage { get; set; }
    }

}
